#include "wx/wxprec.h"
#include <wx/menu.h>
#include <wx/dcmemory.h>
#include <wx/settings.h>
#include <ctype.h>
#include <wx/dynlib.h>
#include <wx/encconv.h>
#include <wx/listctrl.h>
#include <wx/mstream.h>
#include <wx/image.h>
#include <wx/imaglist.h>
#include <wx/tokenzr.h>
#include <wx/rawbmp.h>
#include "Platform.h"
#include "PlatWX.h"
#include "Scintilla.h"
#include "FontQuality.h"
#include "wx/wxscintilla.h"
#include <wx/popupwin.h>

#define wxPy_premultiply(p, a)   ((p) * (a) / 0xff)
#define EXTENT_TEST _T(" `~!@#$%^&*()-_=+\\|[]{};:\"\'<,>.?/1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

#ifdef SCI_NAMESPACE
using namespace Scintilla;
namespace Scintilla {

#endif


  Font::Font() {
    fid = 0;
    ascent = 0;
  }

  Font::~Font() {
    Release();
  }

  void Font::Create( const FontParameters &fp ) {
    Release();
    wxFontEncoding encoding = ( wxFontEncoding )( fp.characterSet - 1 );
    wxFontEncodingArray ea = wxEncodingConverter::GetPlatformEquivalents( encoding );
    if( ea.GetCount() ) {
      encoding = ea[0];
    }
    wxFontWeight weight;
    if( fp.weight <= 300 ) {
      weight = wxFONTWEIGHT_LIGHT;
    } else if( fp.weight >= 700 ) {
      weight = wxFONTWEIGHT_BOLD;
    } else
    { weight = wxFONTWEIGHT_NORMAL; }
    wxFont* font = new wxFont( fp.size,
                               wxFONTFAMILY_DEFAULT,
                               fp.italic ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL,
                               weight,
                               false,
                               sci2wx( fp.faceName ),
                               encoding );
    fid = font;
  }


  void Font::Release() {
    if( fid ) {
      delete reinterpret_cast<wxFont *>( fid );
    }
    fid = 0;
  }

  class SurfaceImpl : public Surface {
    private:
      wxDC*       hDC;
      bool        hDCOwned;
      wxBitmap*   bitmap;
      int         x;
      int         y;
      bool        unicodeMode;

    public:
      SurfaceImpl();
      ~SurfaceImpl();

      virtual void Init( WindowID wid );
      virtual void Init( SurfaceID sid, WindowID wid );
      virtual void InitPixMap( int width, int height, Surface *surface_, WindowID wid );

      virtual void Release();
      virtual bool Initialised();
      virtual void PenColour( ColourDesired fore );
      virtual int LogPixelsY();
      virtual int DeviceHeightFont( int points );
      virtual void MoveTo( int x_, int y_ );
      virtual void LineTo( int x_, int y_ );
      virtual void Polygon( Point *pts, int npts, ColourDesired fore, ColourDesired back );
      virtual void RectangleDraw( PRectangle rc, ColourDesired fore, ColourDesired back );
      virtual void FillRectangle( PRectangle rc, ColourDesired back );
      virtual void FillRectangle( PRectangle rc, Surface &surfacePattern );
      virtual void RoundedRectangle( PRectangle rc, ColourDesired fore, ColourDesired back );
      virtual void AlphaRectangle( PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill,
                                   ColourDesired outline, int alphaOutline, int flags );
      virtual void DrawRGBAImage( PRectangle rc, int width, int height,
                                  const unsigned char *pixelsImage );
      virtual void Ellipse( PRectangle rc, ColourDesired fore, ColourDesired back );
      virtual void Copy( PRectangle rc, Point from, Surface &surfaceSource );

      virtual void DrawTextNoClip( PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back );
      virtual void DrawTextClipped( PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back );
      virtual void DrawTextTransparent( PRectangle rc, Font &font_, XYPOSITION ybase, const char *s, int len, ColourDesired fore );
      virtual void MeasureWidths( Font &font_, const char *s, int len, XYPOSITION *positions );
      virtual XYPOSITION WidthText( Font &font_, const char *s, int len );
      virtual XYPOSITION WidthChar( Font &font_, char ch );
      virtual XYPOSITION Ascent( Font &font_ );
      virtual XYPOSITION Descent( Font &font_ );
      virtual XYPOSITION InternalLeading( Font &font_ );
      virtual XYPOSITION ExternalLeading( Font &font_ );
      virtual XYPOSITION Height( Font &font_ );
      virtual XYPOSITION AverageCharWidth( Font &font_ );

      virtual void SetClip( PRectangle rc );
      virtual void FlushCachedState();

      virtual void SetUnicodeMode( bool unicodeMode_ );
      virtual void SetDBCSMode( int codePage );

      void BrushColour( ColourDesired back );
      void SetFont( Font &font_ );
  };



  SurfaceImpl::SurfaceImpl() :
    hDC( 0 ), hDCOwned( 0 ), bitmap( 0 ),
    x( 0 ), y( 0 ), unicodeMode( 0 ) {}

  SurfaceImpl::~SurfaceImpl() {
    Release();
  }

  void SurfaceImpl::Init( WindowID wid ) {
    InitPixMap( 1, 1, NULL, wid );
  }

  void SurfaceImpl::Init( SurfaceID hDC_, WindowID ) {
    Release();
    hDC = static_cast<wxDC*>( hDC_ );
  }


  void SurfaceImpl::InitPixMap( int width, int height, Surface* surface_, WindowID )

  {
    Release();
    if( surface_ ) {
      hDC = new wxMemoryDC( static_cast<SurfaceImpl*>( surface_ )->hDC );
    } else
    { hDC = new wxMemoryDC(); }
    hDCOwned = true;
    if( width < 1 ) {
      width = 1;
    }
    if( height < 1 ) {
      height = 1;
    }
    bitmap = new wxBitmap( width, height );
    ( static_cast<wxMemoryDC*>( hDC ) )->SelectObject( *bitmap );
  }


  void SurfaceImpl::Release() {
    if( bitmap ) {
      ( static_cast<wxMemoryDC*>( hDC ) )->SelectObject( wxNullBitmap );
      delete bitmap;
      bitmap = 0;
    }
    if( hDCOwned ) {
      delete hDC;
      hDC = 0;
      hDCOwned = false;
    }
  }


  bool SurfaceImpl::Initialised() {
    return hDC != 0;
  }


  void SurfaceImpl::PenColour( ColourDesired fore ) {
    if( fore.AsLong() == -1 ) {
      hDC->SetPen( *wxTRANSPARENT_PEN );
    } else
    { hDC->SetPen( wxPen( wxColourFromCD( fore ) ) ); }
  }

  void SurfaceImpl::BrushColour( ColourDesired back ) {
    if( back.AsLong() == -1 ) {
      hDC->SetBrush( *wxTRANSPARENT_BRUSH );
    } else
    { hDC->SetBrush( wxBrush( wxColourFromCD( back ) ) ); }
  }

  void SurfaceImpl::SetFont( Font &font_ ) {
    if( font_.GetID() ) {
      hDC->SetFont( *( reinterpret_cast<wxFont*>( font_.GetID() ) ) );
    }
  }

  int SurfaceImpl::LogPixelsY() {
    return hDC->GetPPI().y;
  }

  int SurfaceImpl::DeviceHeightFont( int points ) {
    return points;
  }

  void SurfaceImpl::MoveTo( int x_, int y_ ) {
    x = x_;
    y = y_;
  }

  void SurfaceImpl::LineTo( int x_, int y_ ) {
    hDC->DrawLine( x, y, x_, y_ );
    x = x_;
    y = y_;
  }

  void SurfaceImpl::Polygon( Point *pts, int npts, ColourDesired fore, ColourDesired back ) {
    PenColour( fore );
    BrushColour( back );
    wxPoint *p = new wxPoint[npts];
    for( int i = 0; i < npts; i++ ) {
      p[i].x = pts[i].x;
      p[i].y = pts[i].y;
    }
    hDC->DrawPolygon( npts, p );
    delete [] p;
  }

  void SurfaceImpl::RectangleDraw( PRectangle rc, ColourDesired fore, ColourDesired back ) {
    PenColour( fore );
    BrushColour( back );
    hDC->DrawRectangle( wxRectFromPRectangle( rc ) );
  }

  void SurfaceImpl::FillRectangle( PRectangle rc, ColourDesired back ) {
    BrushColour( back );
    hDC->SetPen( *wxTRANSPARENT_PEN );
    hDC->DrawRectangle( wxRectFromPRectangle( rc ) );
  }

  void SurfaceImpl::FillRectangle( PRectangle rc, Surface &surfacePattern ) {
    wxBrush br;
    SurfaceImpl *surface = static_cast<SurfaceImpl *>( &surfacePattern );
    if( surface->bitmap ) {
      br = wxBrush( *( surface->bitmap ) );
    } else
    { br = wxBrush( *wxRED ); }
    hDC->SetPen( *wxTRANSPARENT_PEN );
    hDC->SetBrush( br );
    hDC->DrawRectangle( wxRectFromPRectangle( rc ) );
  }

  void SurfaceImpl::RoundedRectangle( PRectangle rc, ColourDesired fore, ColourDesired back ) {
    PenColour( fore );
    BrushColour( back );
    hDC->DrawRoundedRectangle( wxRectFromPRectangle( rc ), 4 );
  }

  void SurfaceImpl::AlphaRectangle( PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, ColourDesired outline, int alphaOutline, int ) {
    wxUnusedVar( cornerSize );
    wxRect r = wxRectFromPRectangle( rc );
    wxBitmap bmp( r.width, r.height, 32 );
    if( !bmp.IsOk() ) {
      return;
    }
    {
      wxAlphaPixelData pixData( bmp );
      pixData.UseAlpha();
      ColourDesired cdf( fill.AsLong() );
      int red   = cdf.GetRed();
      int green = cdf.GetGreen();
      int blue  = cdf.GetBlue();
      wxAlphaPixelData::Iterator p( pixData );
      for( y = 0; y < r.height; y++ ) {
        p.MoveTo( pixData, 0, y );
        for( x = 0; x < r.width; x++ ) {
          p.Red()   = wxPy_premultiply( red,   alphaFill );
          p.Green() = wxPy_premultiply( green, alphaFill );
          p.Blue()  = wxPy_premultiply( blue,  alphaFill );
          p.Alpha() = alphaFill;
          ++p;
        }
      }
      ColourDesired cdo( outline.AsLong() );
      red   = cdo.GetRed();
      green = cdo.GetGreen();
      blue  = cdo.GetBlue();
      for( x = 0; x < r.width; x++ ) {
        p.MoveTo( pixData, x, 0 );
        if( p.m_ptr ) {
          p.Red()   = wxPy_premultiply( red,   alphaOutline );
          p.Green() = wxPy_premultiply( green, alphaOutline );
          p.Blue()  = wxPy_premultiply( blue,  alphaOutline );
          p.Alpha() = alphaOutline;
          p.MoveTo( pixData, x, r.height - 1 );
          p.Red()   = wxPy_premultiply( red,   alphaOutline );
          p.Green() = wxPy_premultiply( green, alphaOutline );
          p.Blue()  = wxPy_premultiply( blue,  alphaOutline );
          p.Alpha() = alphaOutline;
        }
      }
      for( y = 0; y < r.height; y++ ) {
        p.MoveTo( pixData, 0, y );
        if( p.m_ptr ) {
          p.Red()   = wxPy_premultiply( red,   alphaOutline );
          p.Green() = wxPy_premultiply( green, alphaOutline );
          p.Blue()  = wxPy_premultiply( blue,  alphaOutline );
          p.Alpha() = alphaOutline;
          p.MoveTo( pixData, r.width - 1, y );
          p.Red()   = wxPy_premultiply( red,   alphaOutline );
          p.Green() = wxPy_premultiply( green, alphaOutline );
          p.Blue()  = wxPy_premultiply( blue,  alphaOutline );
          p.Alpha() = alphaOutline;
        }
      }
    }
    if( bmp.IsOk() ) {
      hDC->DrawBitmap( bmp, r.x, r.y, true );
    }
  }

  wxBitmap BitmapFromRGBAImage( int width, int height, const unsigned char *pixelsImage ) {
    int x, y;
    wxBitmap bmp( width, height, 32 );
    if( !bmp.IsOk() ) {
      return wxNullBitmap;
    }
    wxAlphaPixelData pixData( bmp );
    pixData.UseAlpha();
    wxAlphaPixelData::Iterator p( pixData );
    for( y = 0; y < height; y++ ) {
      p.MoveTo( pixData, 0, y );
      for( x = 0; x < width; x++ ) {
        unsigned char red   = *pixelsImage++;
        unsigned char green = *pixelsImage++;
        unsigned char blue  = *pixelsImage++;
        unsigned char alpha = *pixelsImage++;
        p.Red()   = wxPy_premultiply( red,   alpha );
        p.Green() = wxPy_premultiply( green, alpha );
        p.Blue()  = wxPy_premultiply( blue,  alpha );
        p.Alpha() = alpha;
        ++p;
      }
    }
    return bmp;
  }

  void SurfaceImpl::DrawRGBAImage( PRectangle rc, int width, int height, const unsigned char *pixelsImage ) {
    wxRect r = wxRectFromPRectangle( rc );
    wxBitmap bmp = BitmapFromRGBAImage( width, height, pixelsImage );
    if( bmp.IsOk() ) {
      hDC->DrawBitmap( bmp, r.x, r.y, true );
    }
  }

  void SurfaceImpl::Ellipse( PRectangle rc, ColourDesired fore, ColourDesired back ) {
    PenColour( fore );
    BrushColour( back );
    hDC->DrawEllipse( wxRectFromPRectangle( rc ) );
  }

  void SurfaceImpl::Copy( PRectangle rc, Point from, Surface &surfaceSource ) {
    wxRect r = wxRectFromPRectangle( rc );
    hDC->Blit( r.x, r.y, r.width, r.height, ( ( SurfaceImpl& )surfaceSource ).hDC, from.x, from.y, wxCOPY );
  }

  void SurfaceImpl::DrawTextNoClip( PRectangle rc, Font &font, XYPOSITION ybase, const char *s, int len,
                                    ColourDesired fore, ColourDesired back ) {
    SetFont( font );
    hDC->SetTextForeground( wxColourFromCD( fore ) );
    hDC->SetTextBackground( wxColourFromCD( back ) );
    FillRectangle( rc, back );
    hDC->DrawText( sci2wx( s, len ), rc.left, ybase - Ascent( font ) );
  }

  void SurfaceImpl::DrawTextClipped( PRectangle rc, Font &font, XYPOSITION ybase, const char *s, int len, ColourDesired fore, ColourDesired back ) {
    SetFont( font );
    hDC->SetTextForeground( wxColourFromCD( fore ) );
    hDC->SetTextBackground( wxColourFromCD( back ) );
    FillRectangle( rc, back );
    hDC->SetClippingRegion( wxRectFromPRectangle( rc ) );
    hDC->DrawText( sci2wx( s, len ), rc.left, ybase - Ascent( font ) );
    hDC->DestroyClippingRegion();
  }

  void SurfaceImpl::DrawTextTransparent( PRectangle rc, Font &font, XYPOSITION ybase, const char *s, int len, ColourDesired fore ) {
    SetFont( font );
    hDC->SetTextForeground( wxColourFromCD( fore ) );
    hDC->SetBackgroundMode( wxTRANSPARENT );
    hDC->DrawText( sci2wx( s, len ), rc.left, ybase - Ascent( font ) );
    hDC->SetBackgroundMode( wxSOLID );
  }

  void SurfaceImpl::MeasureWidths( Font &font, const char *s, int len, XYPOSITION *positions ) {
    wxString str = sci2wx( s, len );
    wxArrayInt tpos;
    SetFont( font );
    hDC->GetPartialTextExtents( str, tpos );
    int i = 0;
    size_t ui = 0;
    while( i < len ) {
      unsigned char uch = ( unsigned char )s[i];
      positions[i++] = tpos[ui];
      if( uch >= 0x80 ) {
        if( uch < ( 0x80 + 0x40 + 0x20 ) ) {
          positions[i++] = tpos[ui];
        } else {
          positions[i++] = tpos[ui];
          positions[i++] = tpos[ui];
        }
      }
      ui++;
    }
  }

  XYPOSITION SurfaceImpl::WidthText( Font &font, const char *s, int len ) {
    SetFont( font );
    int w;
    int h;
    hDC->GetTextExtent( sci2wx( s, len ), &w, &h );
    return w;
  }

  XYPOSITION SurfaceImpl::WidthChar( Font &font, char ch ) {
    SetFont( font );
    int w;
    int h;
    char s[2] = { ch, 0 };
    hDC->GetTextExtent( sci2wx( s, 1 ), &w, &h );
    return w;
  }

  XYPOSITION SurfaceImpl::Ascent( Font &font ) {
    SetFont( font );
    int w, h, d, e;
    hDC->GetTextExtent( EXTENT_TEST, &w, &h, &d, &e );
    font.ascent = h - d;
    return font.ascent;
  }

  XYPOSITION SurfaceImpl::Descent( Font &font ) {
    SetFont( font );
    int w, h, d, e;
    hDC->GetTextExtent( EXTENT_TEST, &w, &h, &d, &e );
    return d;
  }

  XYPOSITION SurfaceImpl::InternalLeading( Font &( font ) ) {
    return 0;
  }

  XYPOSITION SurfaceImpl::ExternalLeading( Font &font ) {
    SetFont( font );
    int w, h, d, e;
    hDC->GetTextExtent( EXTENT_TEST, &w, &h, &d, &e );
    return e;
  }

  XYPOSITION SurfaceImpl::Height( Font &font ) {
    SetFont( font );
    return hDC->GetCharHeight() + 1;
  }

  XYPOSITION SurfaceImpl::AverageCharWidth( Font &font ) {
    SetFont( font );
    return hDC->GetCharWidth();
  }

  void SurfaceImpl::SetClip( PRectangle rc ) {
    hDC->SetClippingRegion( wxRectFromPRectangle( rc ) );
  }

  void SurfaceImpl::FlushCachedState() {
  }

  void SurfaceImpl::SetUnicodeMode( bool unicodeMode_ ) {
    unicodeMode = unicodeMode_;
  }

  void SurfaceImpl::SetDBCSMode( int ( codePage ) ) {
  }


  Surface *Surface::Allocate( int ( technology ) ) {
    return new SurfaceImpl;
  }
  inline wxWindow* GETWIN( WindowID wid ) { return ( wxWindow* )wid; }

  Window::~Window() {
  }

  void Window::Destroy() {
    if( wid ) {
      Show( false );
      GETWIN( wid )->Destroy();
    }
    wid = 0;
  }

  bool Window::HasFocus() {
    return wxWindow::FindFocus() == GETWIN( wid );
  }

  PRectangle Window::GetPosition() {
    if( ! wid ) {
      return PRectangle();
    }
    wxRect rc( GETWIN( wid )->GetPosition(), GETWIN( wid )->GetSize() );
    return PRectangleFromwxRect( rc );
  }

  void Window::SetPosition( PRectangle rc ) {
    wxRect r = wxRectFromPRectangle( rc );
    GETWIN( wid )->SetSize( r );
  }

  void Window::SetPositionRelative( PRectangle rc, Window ) {
    SetPosition( rc );
  }

  PRectangle Window::GetClientPosition() {
    if( ! wid ) {
      return PRectangle();
    }
    wxSize sz = GETWIN( wid )->GetClientSize();
    return  PRectangle( 0, 0, sz.x, sz.y );
  }

  void Window::Show( bool show ) {
    GETWIN( wid )->Show( show );
  }

  void Window::InvalidateAll() {
    GETWIN( wid )->Refresh( false );
  }

  void Window::InvalidateRectangle( PRectangle rc ) {
    wxRect r = wxRectFromPRectangle( rc );
    GETWIN( wid )->Refresh( false, &r );
  }

  void Window::SetFont( Font &font ) {
    GETWIN( wid )->SetFont( *( ( wxFont* )font.GetID() ) );
  }

  void Window::SetCursor( Cursor curs ) {
    wxStockCursor cursorId;
    switch( curs ) {
      case cursorText:
        cursorId = wxCURSOR_IBEAM;
        break;
      case cursorArrow:
        cursorId = wxCURSOR_ARROW;
        break;
      case cursorUp:
        cursorId = wxCURSOR_ARROW;
        break;
      case cursorWait:
        cursorId = wxCURSOR_WAIT;
        break;
      case cursorHoriz:
        cursorId = wxCURSOR_SIZEWE;
        break;
      case cursorVert:
        cursorId = wxCURSOR_SIZENS;
        break;
      case cursorReverseArrow:
        cursorId = wxCURSOR_RIGHT_ARROW;
        break;
      case cursorHand:
        cursorId = wxCURSOR_HAND;
        break;
      default:
        cursorId = wxCURSOR_ARROW;
        break;
    }
    wxCursor wc = wxCursor( cursorId );
    if( curs != cursorLast ) {
      GETWIN( wid )->SetCursor( wc );
      cursorLast = curs;
    }
  }

  void Window::SetTitle( const char *s ) {
    GETWIN( wid )->SetLabel( sci2wx( s ) );
  }

  PRectangle Window::GetMonitorRect( Point pt ) {
    wxRect rect;
    if( ! wid ) {
      return PRectangle();
    }
    #if wxUSE_DISPLAY
    int n = wxDisplay::GetFromPoint( wxPoint( pt.x, pt.y ) );
    wxDisplay dpy( n == wxNOT_FOUND ? 0 : n );
    rect = dpy.GetGeometry();
    #else
    wxUnusedVar( pt );
    #endif
    return PRectangleFromwxRect( rect );
  }
  #ifdef SCI_NAMESPACE
}
  #endif

class wxSCIListBox : public wxListView {
  public:
    wxSCIListBox( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
      : wxListView() {
      Hide();
      Create( parent, id, pos, size, style );
    }
    void OnFocus( wxFocusEvent& event ) {
      GetParent()->SetFocus();
      event.Skip();
    }

    void OnKillFocus( wxFocusEvent& ( event ) ) {
    }
  private:
    DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE( wxSCIListBox, wxListView )
  EVT_SET_FOCUS( wxSCIListBox::OnFocus )
  EVT_KILL_FOCUS( wxSCIListBox::OnKillFocus )
END_EVENT_TABLE()

class wxSCIListBoxWin : public wxPopupWindow {
  private:
    wxListView* lv;
    CallBackAction doubleClickAction;
    void* doubleClickActionData;
  public:
    wxSCIListBoxWin( wxWindow* parent, wxWindowID id, Point ( location ) ) :
      wxPopupWindow( parent, wxBORDER_NONE ) {
      SetBackgroundColour( *wxBLACK );
      lv = new wxSCIListBox( this, id, wxDefaultPosition, wxDefaultSize,
                             wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxSIMPLE_BORDER );
      lv->SetCursor( wxCursor( wxCURSOR_ARROW ) );
      lv->InsertColumn( 0, wxEmptyString );
      lv->InsertColumn( 1, wxEmptyString );
      #if defined(__WXOSX_COCOA__) || defined(__WXGTK__)
      SetBackgroundColour( wxColour( 0xC0, 0xC0, 0xC0 ) );
      #endif
    }



    virtual void DoSetSize( int x, int y,
                            int width, int height,
                            int sizeFlags = wxSIZE_AUTO ) {
      if( x != wxDefaultCoord ) {
        GetParent()->ClientToScreen( &x, NULL );
      }
      if( y != wxDefaultCoord ) {
        GetParent()->ClientToScreen( NULL, &y );
      }
      wxPopupWindow::DoSetSize( x, y, width, height, sizeFlags );
    }


    virtual void DoGetPosition( int *x, int *y ) const {
      int sx, sy;
      wxPopupWindow::DoGetPosition( &sx, &sy );
      GetParent()->ScreenToClient( &sx, &sy );
      if( x ) {
        *x = sx;
      }
      if( y ) {
        *y = sy;
      }
    }


    bool Destroy() {
      if( !wxPendingDelete.Member( this ) ) {
        wxPendingDelete.Append( this );
      }
      return true;
    }


    int IconWidth() {
      wxImageList* il = lv->GetImageList( wxIMAGE_LIST_SMALL );
      if( il != NULL ) {
        int w, h;
        il->GetSize( 0, w, h );
        return w;
      }
      return 0;
    }


    void SetDoubleClickAction( CallBackAction action, void *data ) {
      doubleClickAction = action;
      doubleClickActionData = data;
    }


    void OnFocus( wxFocusEvent& event ) {
      GetParent()->SetFocus();
      event.Skip();
    }

    void OnSize( wxSizeEvent& event ) {
      wxSize sz = GetClientSize();
      int x, y, w, h;
      x = y = 0;
      w = sz.x;
      h = sz.y;
      #if defined(__WXOSX_COCOA__) || defined(__WXGTK__)
      x = y = 1;
      w -= 2;
      h -= 2;
      #endif
      lv->SetSize( x, y, w, h );
      lv->SetColumnWidth( 0, IconWidth() + 4 );
      lv->SetColumnWidth( 1, w - 2 - lv->GetColumnWidth( 0 ) - wxSystemSettings::GetMetric( wxSYS_VSCROLL_X ) );
      event.Skip();
    }

    void OnActivate( wxListEvent& ( event ) ) {
      doubleClickAction( doubleClickActionData );
    }

    wxListView* GetLB() { return lv; }

  private:
    DECLARE_EVENT_TABLE()

};

BEGIN_EVENT_TABLE( wxSCIListBoxWin, wxPopupWindow )
  EVT_SET_FOCUS( wxSCIListBoxWin::OnFocus )
  EVT_SIZE( wxSCIListBoxWin::OnSize )
  EVT_LIST_ITEM_ACTIVATED( wxID_ANY, wxSCIListBoxWin::OnActivate )
END_EVENT_TABLE()

inline wxSCIListBoxWin* GETLBW( WindowID win ) {
  return ( ( wxSCIListBoxWin* )win );
}

inline wxListView* GETLB( WindowID win ) {
  return GETLBW( win )->GetLB();
}


class ListBoxImpl : public ListBox {
  private:
    int lineHeight;
    bool unicodeMode;
    int desiredVisibleRows;
    int aveCharWidth;
    size_t maxStrWidth;
    Point location;
    wxImageList* imgList;
    wxArrayInt* imgTypeMap;

  public:
    ListBoxImpl();
    ~ListBoxImpl();
    static ListBox *Allocate();

    virtual void SetFont( Font &font );
    virtual void Create( Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_ );
    virtual void SetAverageCharWidth( int width );
    virtual void SetVisibleRows( int rows );
    virtual int GetVisibleRows() const;
    virtual PRectangle GetDesiredRect();
    virtual int CaretFromEdge();
    virtual void Clear();
    virtual void Append( char *s, int type = -1 );
    void Append( const wxString& text, int type );
    virtual int Length();
    virtual void Select( int n );
    virtual int GetSelection();
    virtual int Find( const char *prefix );
    virtual void GetValue( int n, char *value, int len );
    virtual void RegisterImage( int type, const char *xpm_data );
    void RegisterImageHelper( int type, wxBitmap& bmp );
    virtual void RegisterRGBAImage( int type, int width, int height, const unsigned char *pixelsImage );
    virtual void ClearRegisteredImages();
    virtual void SetDoubleClickAction( CallBackAction, void * );
    virtual void SetList( const char* list, char separator, char typesep );
};


ListBoxImpl::ListBoxImpl()
  : lineHeight( 10 ), unicodeMode( false ),
    desiredVisibleRows( 5 ), aveCharWidth( 8 ), maxStrWidth( 0 ),
    imgList( NULL ), imgTypeMap( NULL ) {
}

ListBoxImpl::~ListBoxImpl() {
  wxDELETE( imgList );
  wxDELETE( imgTypeMap );
}


void ListBoxImpl::SetFont( Font &font ) {
  GETLB( wid )->SetFont( *( ( wxFont* )font.GetID() ) );
}


void ListBoxImpl::Create( Window &parent, int ctrlID, Point location_, int lineHeight_, bool unicodeMode_, int technology_ ) {
  location = location_;
  lineHeight =  lineHeight_;
  unicodeMode = unicodeMode_;
  maxStrWidth = 0;
  if( wid == 0 ) {
    wid = new wxSCIListBoxWin( GETWIN( parent.GetID() ), ctrlID, location_ );
  } else if( GETLBW( wid )->GetParent() != GETWIN( parent.GetID() ) ) {
    GETLBW( wid )->Reparent( GETWIN( parent.GetID() ) );
  }
  if( !GETLBW( wid )->IsShown() ) {
    GETLBW( wid )->SetPosition( wxPoint( location_.x, location_.y ) );
  }
  GETLBW( wid )->SetId( ctrlID );
  GETLB( wid )->SetId( ctrlID );
  if( imgList != NULL ) {
    GETLB( wid )->SetImageList( imgList, wxIMAGE_LIST_SMALL );
  }
  ( void ) technology_;
}


void ListBoxImpl::SetAverageCharWidth( int width ) {
  aveCharWidth = width;
}


void ListBoxImpl::SetVisibleRows( int rows ) {
  desiredVisibleRows = rows;
}


int ListBoxImpl::GetVisibleRows() const {
  return desiredVisibleRows;
}

PRectangle ListBoxImpl::GetDesiredRect() {
  static const int cMaxWidth = 1000;
  int maxw = maxStrWidth * aveCharWidth;
  int maxh ;
  if( maxw == 0 ) {
    maxw = 100;
  }
  maxw += aveCharWidth * 3 + GETLBW( wid )->IconWidth() + wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
  if( maxw > cMaxWidth ) {
    maxw = cMaxWidth;
  }
  int count = GETLB( wid )->GetItemCount();
  if( count ) {
    wxRect rect;
    GETLB( wid )->GetItemRect( 0, rect );
    maxh = count * rect.GetHeight();
    if( maxh > desiredVisibleRows * lineHeight ) {
      maxh = desiredVisibleRows * lineHeight;
    }
    int lines = maxh / rect.GetHeight();
    maxh = ( lines + 1 ) * rect.GetHeight() + 2;
  } else {
    maxh = 100;
  }
  PRectangle rc;
  rc.top = 0;
  rc.left = 0;
  rc.right = maxw;
  rc.bottom = maxh;
  return rc;
}


int ListBoxImpl::CaretFromEdge() {
  return 4 + GETLBW( wid )->IconWidth();
}


void ListBoxImpl::Clear() {
  GETLB( wid )->DeleteAllItems();
}


void ListBoxImpl::Append( char *s, int type ) {
  Append( sci2wx( s ), type );
}

void ListBoxImpl::Append( const wxString& text, int type ) {
  long count  = GETLB( wid )->GetItemCount();
  long itemID  = GETLB( wid )->InsertItem( count, wxEmptyString );
  long idx = -1;
  GETLB( wid )->SetItem( itemID, 1, text );
  maxStrWidth = wxMax( maxStrWidth, text.length() );
  if( type != -1 ) {
    wxCHECK_RET( imgTypeMap, _T( "Unexpected NULL imgTypeMap" ) );
    idx = imgTypeMap->Item( type );
  }
  GETLB( wid )->SetItemImage( itemID, idx, idx );
}

void ListBoxImpl::SetList( const char* list, char separator, char typesep ) {
  GETLB( wid )->Freeze();
  Clear();
  wxStringTokenizer tkzr( sci2wx( list ), ( wxChar )separator );
  while( tkzr.HasMoreTokens() ) {
    wxString token = tkzr.GetNextToken();
    long type = -1;
    int pos = token.Find( typesep );
    if( pos != -1 ) {
      token.Mid( pos + 1 ).ToLong( &type );
      token.Truncate( pos );
    }
    Append( token, ( int )type );
  }
  GETLB( wid )->Thaw();
}


int ListBoxImpl::Length() {
  return GETLB( wid )->GetItemCount();
}


void ListBoxImpl::Select( int n ) {
  bool select = true;
  if( n == -1 ) {
    n = 0;
    select = false;
  }
  GETLB( wid )->EnsureVisible( n );
  GETLB( wid )->Focus( n );
  GETLB( wid )->Select( n, select );
}


int ListBoxImpl::GetSelection() {
  return GETLB( wid )->GetFirstSelected();
}


int ListBoxImpl::Find( const char *prefix ) {
  wxListView* list = GETLB( wid );
  wxString sPrefix = unicodeMode ? sci2wx( prefix ) : wxString::From8BitData( prefix );
  for( int i = 0; i < list->GetItemCount(); ++i ) {
    if( list->GetItemText( i ).StartsWith( sPrefix ) ) {
      return i;
    }
  }
  return wxNOT_FOUND;
}


void ListBoxImpl::GetValue( int n, char *value, int len ) {
  wxListItem item;
  item.SetId( n );
  item.SetColumn( 1 );
  item.SetMask( wxLIST_MASK_TEXT );
  GETLB( wid )->GetItem( item );
  strncpy( value, wx2sci( item.GetText() ), len );
  value[len - 1] = '\0';
}

void ListBoxImpl::RegisterImageHelper( int type, wxBitmap& bmp ) {
  if( ! imgList ) {
    imgList = new wxImageList( bmp.GetWidth(), bmp.GetHeight(), true );
    imgTypeMap = new wxArrayInt;
  }
  int idx = imgList->Add( bmp );
  wxArrayInt& itm = *imgTypeMap;
  if( itm.GetCount() < ( size_t )type + 1 ) {
    itm.Add( -1, type - itm.GetCount() + 1 );
  }
  itm[type] = idx;
}

void ListBoxImpl::RegisterImage( int type, const char *xpm_data ) {
  wxMemoryInputStream stream( xpm_data, strlen( xpm_data ) + 1 );
  wxImage img( stream, wxBITMAP_TYPE_XPM );
  wxBitmap bmp( img );
  RegisterImageHelper( type, bmp );
  if( !bmp.IsOk() ) {
    return;
  }
}


void ListBoxImpl::RegisterRGBAImage( int type, int width, int height,
                                     const unsigned char* pixelsImage ) {
  #ifdef wxHAS_RAW_BITMAP
  wxBitmap bmp = BitmapFromRGBAImage( width, height, pixelsImage );
  RegisterImageHelper( type, bmp );
  #endif
  ( void ) type;
  ( void ) width;
  ( void ) height;
  ( void ) pixelsImage;
}


void ListBoxImpl::ClearRegisteredImages() {
  wxDELETE( imgList );
  wxDELETE( imgTypeMap );
  if( wid ) {
    GETLB( wid )->SetImageList( NULL, wxIMAGE_LIST_SMALL );
  }
}


void ListBoxImpl::SetDoubleClickAction( CallBackAction action, void *data ) {
  GETLBW( wid )->SetDoubleClickAction( action, data );
}

ListBox::ListBox() {
}

ListBox::~ListBox() {
}

ListBox *ListBox::Allocate() {
  return new ListBoxImpl();
}


#ifdef SCI_NAMESPACE
namespace Scintilla {
#endif

  Menu::Menu() : mid( 0 ) {
  }

  void Menu::CreatePopUp() {
    Destroy();
    mid = new wxMenu();
  }

  void Menu::Destroy() {
    if( mid ) {
      wxMenu* menu = static_cast<wxMenu *>( mid );
      delete menu;
    }
    mid = 0;
  }

  void Menu::Show( Point pt, Window &w ) {
    GETWIN( w.GetID() )->PopupMenu( static_cast<wxMenu *>( mid ), pt.x - 4, pt.y );
    Destroy();
  }

  class DynamicLibraryImpl : public DynamicLibrary {
    protected:
      wxDynamicLibrary *lib;
    public:
      DynamicLibraryImpl( const char *modulePath ) {
        wxString path = sci2wx( modulePath );
        lib = new wxDynamicLibrary( path );
      }

      virtual ~DynamicLibraryImpl() {
        if( lib && lib->IsLoaded() ) {
          lib->Unload();
        }
        delete lib;
        lib = 0;
      }

      virtual Function FindFunction( const char *name ) {
        wxString symbol = sci2wx( name );
        if( lib && lib->HasSymbol( symbol ) ) {
          return static_cast<Function>( lib->GetSymbol( symbol ) );
        }
        return NULL;
      }

      virtual bool IsValid() {
        if( lib && lib->IsLoaded() ) {
          return true;
        }
        return false;
      }
  };

  DynamicLibrary *DynamicLibrary::Load( const char *modulePath ) {
    return static_cast<DynamicLibrary *>( new DynamicLibraryImpl( modulePath ) );
  }

  ColourDesired Platform::Chrome() {
    wxColour c;
    c = wxSystemSettings::GetColour( wxSYS_COLOUR_3DFACE );
    return ColourDesired( c.Red(), c.Green(), c.Blue() );
  }

  ColourDesired Platform::ChromeHighlight() {
    wxColour c;
    c = wxSystemSettings::GetColour( wxSYS_COLOUR_3DHIGHLIGHT );
    return ColourDesired( c.Red(), c.Green(), c.Blue() );
  }


#define wxCRT_StrlenA    strlen
#define wxCRT_StrncpyA   strncpy


  inline size_t wxStrlcpy( char *dest, const char *src, size_t n ) {
    const size_t len = wxCRT_StrlenA( src );
    if( n ) {
      if( n-- > len ) {
        n = len;
      }
      wxCRT_StrncpyA( dest, src, n );
      dest[n] = '\0';
    }
    return len;
  }
#undef wxCRT_StrlenA
#undef wxCRT_StrncpyA

  const char *Platform::DefaultFont() {
    static char buf[128];
    wxStrlcpy( buf, wxNORMAL_FONT->GetFaceName().mbc_str(), WXSIZEOF( buf ) );
    return buf;
  }

  int Platform::DefaultFontSize() {
    return wxNORMAL_FONT->GetPointSize();
  }

  unsigned int Platform::DoubleClickTime() {
    return 500;
  }

  bool Platform::MouseButtonBounce() {
    return false;
  }

  bool Platform::IsKeyDown( int ( key ) ) {
    return false;
  }

  long Platform::SendScintilla( WindowID w, unsigned int msg, unsigned long wParam, long lParam ) {
    wxScintilla* sci = ( wxScintilla* )w;
    return sci->SendMsg( msg, ( wxUIntPtr )wParam, ( wxIntPtr )lParam );
  }

  long Platform::SendScintillaPointer( WindowID w, unsigned int msg, unsigned long wParam, void *lParam ) {
    wxScintilla* sci = static_cast<wxScintilla*>( w );
    return sci->SendMsg( msg, ( wxUIntPtr )wParam, ( wxIntPtr )lParam );
  }

  int Platform::Minimum( int a, int b ) {
    if( a < b ) {
      return a;
    } else
    { return b; }
  }

  int Platform::Maximum( int a, int b ) {
    if( a > b ) {
      return a;
    } else
    { return b; }
  }

#define TRACE

  #ifdef TRACE
#include <wx/log.h>
  #endif

  void Platform::DebugDisplay( const char *s ) {
    #ifdef TRACE
    wxSafeShowMessage( _T( "wxScintilla" ), sci2wx( s ) );
    #else
    wxUnusedVar( s );
    #endif
  }

  void Platform::DebugPrintf( const char *format, ... ) {
    #ifdef TRACE
    char buffer[2000];
    va_list pArguments;
    va_start( pArguments, format );
    vsprintf( buffer, format, pArguments );
    va_end( pArguments );
    Platform::DebugDisplay( buffer );
    #else
    wxUnusedVar( format );
    #endif
  }


  static bool assertionPopUps = true;

  bool Platform::ShowAssertionPopUps( bool assertionPopUps_ ) {
    bool ret = assertionPopUps;
    assertionPopUps = assertionPopUps_;
    return ret;
  }

  void Platform::Assert( const char *c, const char *file, int line ) {
    #ifdef TRACE
    char buffer[2000];
    sprintf( buffer, "Assertion [%s] failed at %s %d", c, file, line );
    if( assertionPopUps ) {
      wxSafeShowMessage( _T( "wxScintilla" ), sci2wx( buffer ) );
    } else {
      strcat( buffer, "\r\n" );
      Platform::DebugDisplay( buffer );
      abort();
    }
    #else
    wxUnusedVar( c );
    wxUnusedVar( file );
    wxUnusedVar( line );
    #endif
  }


  int Platform::Clamp( int val, int minVal, int maxVal ) {
    if( val > maxVal ) {
      val = maxVal;
    }
    if( val < minVal ) {
      val = minVal;
    }
    return val;
  }

  bool Platform::IsDBCSLeadByte( int codePage, char ch ) {
    unsigned char uch = static_cast<unsigned char>( ch );
    switch( codePage ) {
      case 932:
        return ( ( uch >= 0x81 ) && ( uch <= 0x9F ) ) ||
               ( ( uch >= 0xE0 ) && ( uch <= 0xEF ) );
      case 936:
        return ( uch >= 0x81 ) && ( uch <= 0xFE );
      case 949:
        return ( uch >= 0x81 ) && ( uch <= 0xFE );
      case 950:
        return ( uch >= 0x81 ) && ( uch <= 0xFE );
      case 1361:
        return
          ( ( uch >= 0x84 ) && ( uch <= 0xD3 ) ) ||
          ( ( uch >= 0xD8 ) && ( uch <= 0xDE ) ) ||
          ( ( uch >= 0xE0 ) && ( uch <= 0xF9 ) );
    }
    return false;
  }

  int Platform::DBCSCharLength( int codePage, const char *s ) {
    if( codePage == 932 || codePage == 936 || codePage == 949 ||
        codePage == 950 || codePage == 1361 ) {
      return IsDBCSLeadByte( codePage, s[0] ) ? 2 : 1;
    }
    return 1;
  }

  int Platform::DBCSCharMaxLength() {
    return 1;
  }


  ElapsedTime::ElapsedTime() {
    wxLongLong localTime = wxGetLocalTimeMillis();
    littleBit = localTime.GetLo();
    bigBit = localTime.GetHi();
  }

  double ElapsedTime::Duration( bool reset ) {
    wxLongLong prevTime( bigBit, littleBit );
    wxLongLong localTime = wxGetLocalTimeMillis();
    if( reset ) {
      littleBit = localTime.GetLo();
      bigBit = localTime.GetHi();
    }
    wxLongLong duration = localTime - prevTime;
    double result = duration.ToDouble();
    result /= 1000.0;
    return result;
  }


  #ifdef SCI_NAMESPACE
}
  #endif

#include "UniConversion.h"

wxString sci2wx( const char* str, size_t len ) {
  if( !len ) {
    return wxEmptyString;
  }
  size_t wclen = UTF16Length( str, len );
  wxWCharBuffer buffer( wclen + 1 );
  size_t actualLen = UTF16FromUTF8( str, len, buffer.data(), wclen + 1 );
  return wxString( buffer.data(), actualLen );
}

wxString sci2wx( const char* str ) {
  return sci2wx( str, strlen( str ) );
}

const wxWX2MBbuf wx2sci( const wxString& str ) {
  const wchar_t* wcstr = str.c_str();
  size_t wclen = str.length();
  size_t len = UTF8Length( wcstr, wclen );
  wxCharBuffer buffer( len + 1 );
  UTF8FromUTF16( wcstr, wclen, buffer.data(), len );
  return buffer;
}
